package com.utils.db;

import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.support.JdbcUtils;

import com.utils.StringUtils;

import javax.persistence.Column;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.*;
import java.util.Arrays;

/**
 * 行数据转对象处理器
 *
 * @author: dingjiejun
 * @version: 1.0
 * @date: 2017/3/23
 */
public class ResultSetToBeanProcess {

    private static Logger logger = LoggerFactory.getLogger(ResultSetToBeanProcess.class);

    protected static final int COLUMN_NOT_FOUND = -1;

    private int[] propertyToColumn;

    /**
     * 将结果集转换为javabean
     *
     * @param rs
     * @param clazz
     * @return
     * @throws SQLException
     */
    public <T> T toBean(ResultSet rs, Class<T> clazz) throws SQLException {

        PropertyDescriptor[] props = PropertyUtils.getPropertyDescriptors(clazz);
        ResultSetMetaData rsmd = rs.getMetaData();
        if (propertyToColumn == null) {
            mapColumnsToProperties(rsmd, props);
        }
        T bean = ClassUtils.getInstance(clazz);
        for (int i = 0; i < propertyToColumn.length; i++) {
            if (propertyToColumn[i] != COLUMN_NOT_FOUND) {
                PropertyDescriptor prop = props[i];
                Method setter = prop.getWriteMethod();
                Class<?> propType = setter.getParameterTypes()[0];
                if (propType.isPrimitive()) {
                    propType = ClassUtils.primitiveToWrapper(propType);
                }
                Object value = processColumn(rs, propertyToColumn[i], propType);
                if ((value instanceof java.util.Date)) {
                    String typeName = propType.getName();
                    value = convertValueByType(value, typeName);
                } else {

                }
                try {
                    setter.invoke(bean, value);
                } catch (InvocationTargetException e) {
                    throw new DatabaseException("Cannot set Value[" + value + "] to Property[" + prop.getName() + "]",
                            e);
                } catch (IllegalAccessException e) {
                    throw new DatabaseException("Cannot set Value[" + value + "] to Property[" + prop.getName() + "]",
                            e);
                }
            }
        }
        return bean;
    }

    private Object convertValueByType(Object value, String typeName) {
        if ("java.sql.Date".equals(typeName)) {
            return new Date(((java.util.Date) value).getTime());
        } else if ("java.sql.Time".equals(typeName)) {
            return new Time(((java.util.Date) value).getTime());
        } else if ("java.sql.Timestamp".equals(typeName)) {
            Timestamp tsValue = (Timestamp) value;
            int nanos = tsValue.getNanos();
            value = new Timestamp(tsValue.getTime());
            ((Timestamp) value).setNanos(nanos);
            return value;
        }
        return value;
    }

    @SuppressWarnings("unchecked")
    public <T> T toPrimitive(ResultSet rs, Class<T> clazz) throws SQLException {
        Object value = processColumn(rs, 1, clazz);
        if ((value instanceof java.util.Date)) {
            String typeName = clazz.getName();
            value = convertValueByType(value, typeName);
        }
        return (T) value;

    }

    protected Object processColumn(ResultSet rs, int index, Class<?> propType) throws SQLException {
        if ((!propType.isPrimitive()) && (rs.getObject(index) == null)) {
            return null;
        }

        if (propType.equals(String.class)) {
            return rs.getString(index);
        }
        if (propType.equals(Integer.class)) {
            return rs.getInt(index);
        }
        if (propType.equals(Long.class)) {
            return rs.getLong(index);
        }
        if (propType.equals(Float.class)) {
            return rs.getFloat(index);
        }
        if (propType.equals(Double.class)) {
            return rs.getDouble(index);
        }
        if (propType.equals(BigDecimal.class)) {
            return rs.getBigDecimal(index);
        }
        if (propType.equals(Boolean.class)) {
            return rs.getBoolean(index);
        }
        if (propType.equals(Short.class)) {
            return rs.getShort(index);
        }
        if (propType.equals(Byte.class)) {
            return rs.getByte(index);
        }
        if (propType.equals(Timestamp.class)) {
            return rs.getTimestamp(index);
        }
        if (propType.equals(Time.class)) {
            return rs.getTime(index);
        }
        if (propType.equals(SQLXML.class)) {
            return rs.getSQLXML(index);
        }
        return rs.getObject(index);
    }

    /**
     * 将数据库字段和bean属性对应起来 数据库字段去除下划线后，与bean属性做忽略大小写比较
     *
     * @param rsmd
     * @param props
     * @return
     * @throws SQLException
     */
    protected void mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor[] props) throws SQLException {

        int propCount = props.length;
        propertyToColumn = new int[propCount];
        Arrays.fill(propertyToColumn, COLUMN_NOT_FOUND);

        for (int i = 0; i < propCount; i++) {
            String propName = props[i].getName();
            if ("class".equals(propName)) {
                continue;
            }
            Method setter = props[i].getWriteMethod();
            if (setter == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug(String.format("not setter of property %s", propName));
                }
            } else {
                Method getter = props[i].getReadMethod();
                boolean isColumnDefined = false;
                if (getter != null) {
                    Column column = getter.getAnnotation(Column.class);
                    if (column != null && StringUtils.isNotBlank(column.name())) {
                        propName = column.name();
                        isColumnDefined = true;
                    }
                }

                // 字段比较
                int colCount = rsmd.getColumnCount();
                for (int k = 1; k <= colCount; k++) {
                    String colName = JdbcUtils.lookupColumnName(rsmd, k);
                    if (!isColumnDefined) {
                        colName = StringUtils.remove(colName, '_');
                    }
                    if (propName.equalsIgnoreCase(colName)) {
                        propertyToColumn[i] = k;
                        break;
                    }
                }

            }
        }

    }

}

